java之多态、类实例化过程

您所在的位置:网站首页 java 类的实例化在编译前处理 java之多态、类实例化过程

java之多态、类实例化过程

2024-04-06 13:06| 来源: 网络整理| 查看: 265

什么是多态为什么要有多态 一些理解多态的表述构造器和多态编写构造器的有效准则实例化一个对象的过程创建子类对象时会创建父类对象吗? 参考

什么是多态

不局限于特定编程语言中,我们先从概念上理解多态。 多态是同一个行为具有多个不同表现形式或者形态的能力;也就是,同一个行为在不同情况下可以表现出不同的结果。多态分离了做什么(what)和怎么做(how),即分离了接口和实现,接口定义了做什么(行为),实现来觉得怎么做(表现形式)。 具体到语言当中,多态实现的前提是继承,继承允许将对象视为它自己本身的类型或者其基类类型来加以处理。java语言中使用多态,在编译期是不知道对象引用变量类型的,通过方法调用只知道要做什么,运行期通过动态绑定技术,发现引用指向对象的实际类型,进而找到需要调用的方法实体,知道了要怎么做。 怎么实现动态绑定的,每个语言都是不一样的,对于java是jvm实现的,主要是通过对字节码中常量表的解析来实现的,具体去需要jvm和字节码相关知识。

为什么要有多态

多态不但能够改善代码的组织结构和可读性,还能够创建可扩展的程序。

一些理解多态的表述 在面向对象的程序设计语言中,多态是继数据抽象(封装)和继承之后的第三种基本特征。对象既可以作为它自己本身的类型使用,也可以作为它的基类型使用。将一个方法调用同一个方法主体关联起来被称作绑定。 在程序执行前进行绑定叫做前期绑定。前期绑定是面向过程的语言中不需要选择就默认的绑定方式;例如,C只有一种方法调用,那就是前期绑定。在运行时根据对象的类型进行绑定叫做后前绑定;后期绑定也叫做动态绑定或运行时绑定。如果一种语言想实现后前绑定,就必须具有某种机制,以便在运行时能判断对象的类型,从而调用恰当的方法。也就是说,编译器一直不知道对象的具体类型,但是方法调用机制能找到正确的方法体并加以调用。后期绑定机制随编程语言的不同而有所不同,但是只要想一下就会得知,不管怎么样都必须在对象中安置某种“类型信息”。Java中除了static方法和final方法(private方法属于final方法)之外,其它所有的方法都是后期绑定。某个方法声明为final,它可以防止其他人覆盖该方法,更重要的一点是,这样做可以有效地“关闭”动态绑定。由于多态机制,程序只与基类接口通信,这样的程序是可扩展的,因为可以从通用的基类继承出新的数据类型,从而新添一些功能。那些操纵基类接口的方法不需要任何改动就可以应用于新类。多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。 构造器和多态

构造器是不具有多态性的,构造器实际上是static方法,只不过该static声明是隐式的; 但是在构造器中会使用多态,这种情况下就会出现问题。我们看下面的例子:

//: polymorphism/PolyConstructors.java // Constructors and polymorphism // don’t produce what you might expect. import static net.mindview.util.Print.*; class Glyph { void draw() { print("Glyph.draw()"); } Glyph() { print("Glyph() before draw()"); draw(); print("Glyph() after draw()"); } } class RoundGlyph extends Glyph { private int radius = 1; RoundGlyph(int r) { radius = r; print("RoundGlyph.RoundGlyph(), radius = " + radius); } void draw() { print("RoundGlyph.draw(), radius = " + radius); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); } } /* Output: Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5 *///:~

看Glyph类构造器中draw方法的调用,它其实完整的语法是this.draw(),我们知道this是指向当前对象的引用,进一步来说,this指向的当前对象在编译期是不知道的,只有到运行期才知道,但是在编译期this的类型是Glyph。在实例化RoundGlyph对象的时候,是会执行父类Glyph的构造方法的,也就是说在允许期执行this.draw()的时候,this指向的对象是RoundGlyph对象,所以执行的draw方法是RoundGlyph重写的方法,但是此时RoundGlyph对象还没有完全实例化完,所以就出问题了。 使用this或者隐式使用this的情况的也会发生多态。 如果构造器内部调用一个动态绑定方法,就要用到那个方法被覆盖后的定义。然而,这个调用的效果可能相当难于预料,因为被覆盖的方法在对象被完全构造之前就会被调用。这可能会造成一些难于发现的隐藏错误。

编写构造器的有效准则

“用尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其他方法。” 也就是说在构造器中做的工作要简单一些,如果调用方法的话,避免调用可以覆盖的方法,可以调用final方法(也适用private方法,它们自动属于final方法),因为final方法不能被重写。

实例化一个对象的过程

一般实例化一个对象,我们采用new的方式,反射也可以。

在其它任何事物发生之前,将分配给对象的存储空间初始化成二进制的零。 对象中所有的基本类型都会被设为默认值,对象引用被设为null,这是通过将对象内存设为二进制零值而一举生成的。调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层类,等待,直到最底层的导出类。按声明顺序调用成员的初始化方法。调用导出类构造器的主体。

有几个问题: 存储空间初始化成二进制的零,是初始化子类的字段,父类的字段是在什么时候初始化的呢? 我理解,给对象分配存储空间的时候,是包含父类字段空间的。 调用构造器,一定会生成对象实例吗?看下面的章节

这部分内容看《Think in java》 7.9初始化及类的加载、8.3构造器和多态

创建子类对象时会创建父类对象吗?

首先给出答案,创建子类对象时是不会创建父类对象的。 我们看上面实例化一个对象的过程,知道实例化一个子类对象的时候,是会调用父类构造器的,调用父类构造器不等于实例化对象,只有new的时候,分配了对象的存储空间才会创建对象。 调用父类的构造器,目的是对父类中的成员对象进行初始化;成员对象指的是类中的字段,当然是属于实例的字段;有一种说法是,编译期会把成员对象的初始化合并到构造器中;总之,表象来看,执行构造器时,是先对成员对象初始化,再执行构造器里的代码。 为啥要对先对父类中的成员对象进行初始化呢? 我们知道子类对象是可以调用父类中的public和protected方法的,这些方法里面可能使用了父类中定义的字段,为了保证子类对象调用父类中的方法不出错,得提前对父类中的成员对象进行初始化。 还有需要说明的是,父类和父类的父类中的成员对象都是存在子类对象的存储空间中的,始终只有一个对象。

我们知道子类可以覆盖(重写)父类中的方法,直接调用被重写方法的时候,由于在运行时动态绑定的机制,执行的总是子类中重写后的方法;我们要想明确使用父类中方法,就得使用super关键字。 那么这个super的深层含义又是什么呢? 根据上面的表述,可以肯定的是super不是父类对象,这是因为根本没有父类对象,只有一个子类对象。 super这个关键字只不过是访问了这个子类对象空间中特定部分的数据(也就是专门存储父类数据的内存部分)

参考

Java 多态 java中,创建子类对象时,父类对象会也被一起创建么? Java 创建子类实例时会创建父类实例吗?



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3